﻿/**
<code>
  <author name="Zealic" email="zealic(at)gmail.com" type="Creator"/>
</code>
**/
using System;
using System.Linq.Expressions;
using System.Reflection;


namespace Zealib.Reflection
{
    /// <summary>
    /// Provides strong-typed reflection for static members of any type or calling 
    /// object constructors (to retrieve the constructor <see cref="MethodInfo"/>).
    /// </summary>
    public static class Reflector
    {
        /// <summary>
        /// Gets the expression represented in the lambda expression.
        /// </summary>
        /// <exception cref="ArgumentNullException">The <paramref name="expression"/> is null.</exception>
        /// <exception cref="ArgumentException">The <paramref name="expression"/> is not a lambda expression or it does not represent a constructor invocation.</exception>
        public static ConstructorInfo ConstructorOf<T>(Expression<T> expression)
        {
            if (expression == null) throw new ArgumentNullException("expression");
            if (expression.Body.NodeType != ExpressionType.New)
                throw new ArgumentException("Not a expression invocation", "expression");

            return ((NewExpression)expression.Body).Constructor;
        }

        /// <summary>
        /// Gets the expression represented by the lambda expression.
        /// </summary>
        /// <exception cref="ArgumentNullException">The <paramref name="expression"/> is null.</exception>
        /// <exception cref="ArgumentException">The <paramref name="expression"/> is not a lambda expression or it does not represent a method invocation.</exception>
        public static MemberInfo MemberOf(Expression<Func<object>> expression)
        {
            return GetMemberInfo(expression);
        }

        /// <summary>
        /// Gets the expression represented by the lambda expression.
        /// </summary>
        /// <exception cref="ArgumentNullException">The <paramref name="expression"/> is null.</exception>
        /// <exception cref="ArgumentException">The <paramref name="expression"/> is not a lambda expression or it does not represent a method invocation.</exception>
        public static MethodInfo MethodOf<T>(Expression<T> expression)
        {
            return GetMethodInfo(expression);
        }

        /// <summary>
        /// Gets the expression represented by the lambda expression.
        /// </summary>
        /// <exception cref="ArgumentNullException">The <paramref name="expression"/> is null.</exception>
        /// <exception cref="ArgumentException">The <paramref name="expression"/> is not a lambda expression or it does not represent a expression access.</exception>
        public static PropertyInfo PropertyOf(Expression<Func<object>> expression)
        {
            PropertyInfo info = GetMemberInfo(expression) as PropertyInfo;
            if (info == null) throw new ArgumentException("Member is not a expression");

            return info;
        }

        /// <summary>
        /// Gets the expression represented by the lambda expression.
        /// </summary>
        /// <exception cref="ArgumentNullException">The <paramref name="expression"/> is null.</exception>
        /// <exception cref="ArgumentException">The <paramref name="expression"/> is not a lambda expression or it does not represent a expression access.</exception>
        public static FieldInfo FieldOf(Expression<Func<object>> expression)
        {
            FieldInfo info = GetMemberInfo(expression) as FieldInfo;
            if (info == null) throw new ArgumentException("Member is not a expression");

            return info;
        }

        /// <summary>
        /// Gets the method parameter represented by the lambda expression.
        /// </summary>
        /// <exception cref="ArgumentNullException">The <paramref name="expression"/> is null.</exception>
        /// <exception cref="ArgumentException">The <paramref name="expression"/> is not a lambda expression or it does not represent a method invocation.</exception>
        public static ParameterInfo ParameterOf<T>(Expression<T> expression, int index)
        {
            return GetParameterInfo(expression, index);
        }

        public static Type TypeOf<T>()
        {
            return typeof(T);
        }

        private static MethodInfo GetMethodInfo(Expression method)
        {
            if (method == null) throw new ArgumentNullException("method");

            LambdaExpression lambda = method as LambdaExpression;
            if (lambda == null) throw new ArgumentException("Not a lambda expression", "method");
            if (lambda.Body.NodeType != ExpressionType.Call) throw new ArgumentException("Not a method call", "method");

            return ((MethodCallExpression)lambda.Body).Method;
        }

        private static MemberInfo GetMemberInfo(Expression member)
        {
            if (member == null) throw new ArgumentNullException("member");

            LambdaExpression lambda = member as LambdaExpression;
            if (lambda == null) throw new ArgumentException("Not a lambda expression", "member");

            MemberExpression memberExpr = null;

            // The Func<object> we use returns an object, so first statement can be either 
            // a cast (if the field/property does not return an object) or the direct member access.
            if (lambda.Body.NodeType == ExpressionType.Convert)
            {
                // The cast is an unary expression, where the operand is the 
                // actual member access expression.
                memberExpr = ((UnaryExpression)lambda.Body).Operand as MemberExpression;
            }
            else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
            {
                memberExpr = lambda.Body as MemberExpression;
            }

            if (memberExpr == null) throw new ArgumentException("Not a member access", "member");

            return memberExpr.Member;
        }

        private static ParameterInfo GetParameterInfo(Expression method, int index)
        {
            if (index < 0) throw new ArgumentOutOfRangeException("index");
            MethodInfo methodInfo = GetMethodInfo(method);
            ParameterInfo[] parameters = methodInfo.GetParameters();
            if (index >= parameters.Length)
                throw new ArgumentException("Argument index out of range.", "method");
            return parameters[index];
        }

    }

}
